package org.jdesktop.beans;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

//import org.kohsuke.MetaInfServices;

/**
 * An annotation processor that creates or updates a manifest with Java-Bean
 * information.
 * 
 * @author kschaefer
 */
@SuppressWarnings("nls")
// @MetaInfServices(Processor.class)
public class JavaBeanProcessor extends AbstractProcessor {
	@Override
	public Set<String> getSupportedAnnotationTypes() {
		return Collections.singleton(JavaBean.class.getName());
	}

	@Override
	@SuppressWarnings("unchecked")
	public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
		if (roundEnv.processingOver()) {
			return false;
		}

		Set<String> beans = new TreeSet<String>();

		// JavaBean is a type only annotation, so cast to TypeElement is safe
		for (TypeElement type : (Set<TypeElement>) roundEnv.getElementsAnnotatedWith(JavaBean.class)) {
			beans.add(type.getQualifiedName().toString());
		}

		// remove any existing values; we append to the file
		Filer filer = processingEnv.getFiler();

		FileObject manifest = null;

		try {
			processingEnv.getMessager().printMessage(Kind.NOTE, "Attempting to open manifest...");

			manifest = filer.getResource(StandardLocation.SOURCE_PATH, "", "META-INF/MANIFEST.MF");

			if (manifest != null) {
				processingEnv.getMessager().printMessage(Kind.NOTE, "Succeeded: " + manifest.getName());

				BufferedReader r = null;

				try {
					processingEnv.getMessager().printMessage(Kind.NOTE, "Attempting to find previously defined Java beans");

					r = new BufferedReader(new InputStreamReader(manifest.openInputStream(), "UTF-8"));

					String possibleBean = null;

					for (String line = r.readLine(); line != null; line = r.readLine()) {
						if (possibleBean == null) {
							if (line.startsWith("Name: ") && line.endsWith(".class")) {
								possibleBean = line.substring("Name: ".length(), line.length() - ".class".length()).replace(
										'/', '.');

								try {
									Class.forName(possibleBean);
								} catch (ClassNotFoundException notABean) {
									possibleBean = null;
								}
							}
						} else {
							if (line.equals("Java-Bean: True")) {
								processingEnv.getMessager().printMessage(Kind.NOTE, possibleBean + " already defined");
								beans.remove(possibleBean);
							}

							possibleBean = null;
						}
					}

					r.close();

				} catch (FileNotFoundException ignore) {
					processingEnv.getMessager().printMessage(Kind.NOTE, "Manifest not found");
				} catch (IOException e) {
					throw new RuntimeException("Failed to read current Java-Bean information", e);
				} finally {
					if (r != null) {
						try {
							r.close();
						} catch (IOException ignore) {
						}
					}
				}
			}
		} catch (FileNotFoundException ignore) {
			// no file to process
			processingEnv.getMessager().printMessage(Kind.NOTE, "Manifest does not exist...");
		} catch (IOException e) {
			processingEnv.getMessager().printMessage(Kind.ERROR,
					"Failed to load existing manifest for Java-Bean processing:\n" + e);

			return false;
		}

		try {
			processingEnv.getMessager().printMessage(Kind.NOTE, "Attempting to create output manifest...");

			manifest = filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "META-INF/MANIFEST.MF");

			processingEnv.getMessager().printMessage(Kind.NOTE, "Succeeded: " + manifest.getName());
		} catch (IOException e) {
			processingEnv.getMessager().printMessage(Kind.ERROR, "Cannot create manifest for Java-Bean processing:\n" + e);

			return false;
		}

		processingEnv.getMessager().printMessage(Kind.NOTE, "Appending Java-Beans to MANIFEST.MF");
		processingEnv.getMessager().printMessage(Kind.NOTE, beans.toString());

		PrintWriter pw = null;

		try {
			pw = new PrintWriter(new OutputStreamWriter(manifest.openOutputStream(), "UTF-8"));

			pw.println();

			for (String value : beans) {
				pw.println("Name: " + value + ".class");
				pw.println("Java-Bean: True");
				pw.println();
			}
		} catch (IOException e) {
			throw new RuntimeException("Failed to write Java-Bean information", e);
		} finally {
			if (pw != null) {
				pw.close();
			}
		}

		return false;
	}
}
